﻿using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Globalization;
using System.Linq;
using Notoric.Commanding.CommandBase;
using Notoric.Model.Utils;

namespace Notoric.ViewModel.Elements.CWNotation
{
    /// <summary>
    /// Adapts a <see cref="Model.Elements.CWNotation.Chord"/> instance to a Windows Presentation Foundation view.
    /// </summary>
    public class Chord : DurationalSymbol
    {
        #region Constructors

        /// <summary>
        /// Creates a new <see cref="Chord"/> instance.
        /// </summary>
        /// <param name="owner">
        /// <para>
        /// Type: <see cref="Staff"/>
        /// </para>
        /// <para>
        /// The element that directly owns this element.
        /// </para>
        /// </param>
        /// <param name="adaptedElement">
        /// <para>
        /// Type: <see cref="Model.Elements.CWNotation.Chord"/>
        /// </para>
        /// <para>
        /// The adapted model element instance.
        /// </para>
        /// </param>
        /// <exception cref="ArgumentNullException">
        /// <paramref name="adaptedElement"/> is <see langword="null"/>.
        /// </exception>
        /// <exception cref="NullReferenceException">
        /// <paramref name="owner"/> is <see langword="null"/>.
        /// The values of the <see cref="ViewModelElement.UndoStack"/> and <see cref="ViewModelElement.RedoStack"/> properties
        /// are passed directly to the base class (<see cref="ViewModelElement"/>) constructor. Thus, there is not
        /// an occasion to react with a more appropriate exception.
        /// </exception>
        public Chord(Staff owner, Model.Elements.CWNotation.Chord adaptedElement)
            : base(owner, adaptedElement)
        {
            flagsCollection = new ObservableCollection<Flag>();
            flags = new ReadOnlyObservableCollection<Flag>(flagsCollection);

            SetFlags();

            notesCollection = new ObservableCollection<Note>();
            notes = new ReadOnlyObservableCollection<Note>(notesCollection);

            foreach (var note in adaptedElement.Notes)
            {
                var vmNote = new Note(this, note);
                vmNote.PropertyChanged += new PropertyChangedEventHandler(note_PropertyChanged);
                notesCollection.Add(vmNote);
            }

            adaptedElement.Notes.CollectionChanged += new EventHandler<Model.Collections.NotifyCollectionChangedEventArgs>(Notes_CollectionChanged);

            ledgersCollection = new ObservableCollection<LedgerLine>();
            ledgerLines = new ReadOnlyObservableCollection<LedgerLine>(ledgersCollection);
            ledgersBellowIndex = 0;

            SetLedgers();
            LayoutArticulation();

            PropertyChanged += new PropertyChangedEventHandler(Chord_PropertyChanged);
        }

        #endregion

        #region ViewModelElement Members

        /// <inheritdoc/>
        public override string ElementNameKey
        {
            get { return elementNameKey; }
        }

        /// <inheritdoc/>
        public override void Dispose()
        {
            foreach (var note in notesCollection)
                note.Dispose();
            foreach (var flag in flagsCollection)
                flag.Dispose();
            foreach (var line in ledgersCollection)
                line.Dispose();
            base.Dispose();
        }

        #endregion

        #region DurationalSymbol Members

        /// <inheritdoc/>
        public override double DotsOffset
        {
            get { return (double)LengthConverter.Convert(((Model.Elements.CWNotation.Chord)AdaptedElement).GetSizes(Model.Elements.CWNotation.ChordPart.RightPart), typeof(double), null, CultureInfo.CurrentCulture); }
        }

        #endregion

        #region Event Handlers

        /// <inheritdoc/>
        protected override bool AdaptedElement_PropertyChanged(PropertyChangedEventArgs e)
        {
            if (e.PropertyName == "BasicDuration")
            {
                SetFlags();
                RaisePropertyChanged("DotsOffset");
            }
            else if (e.PropertyName == "StemLength")
                RaisePropertyChanged("StemEnd");
            else if (e.PropertyName == "StemDirection")
            {
                RaisePropertyChanged("StemStart");
                RaisePropertyChanged("StemEnd");
                RaisePropertyChanged("DotsOffset");
            }
            else if (e.PropertyName == "GroupBeam")
                SetFlags();
            
            return base.AdaptedElement_PropertyChanged(e);
        }

        /// <summary>
        /// Occurs when a property of this chord has changed.
        /// </summary>
        /// <remarks>
        /// <b>Operation:</b><br/>
        /// If the property is <see cref="CoreSymbol.VerticalPosition"/> the handler raises
        /// the <see cref="ViewModelElement.PropertyChanged"/> event for <see cref="StemStart"/>
        /// and <see cref="StemEnd"/> properties.
        /// </remarks>
        /// <param name="sender">Object that has sent the event</param>
        /// <param name="e">Event data</param>
        private void Chord_PropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            if (e.PropertyName == "VerticalPosition")
            {
                RaisePropertyChanged("StemStart");
                RaisePropertyChanged("StemEnd");
            }
            else if (e.PropertyName == "StemEnd")
                LayoutArticulation();
            else if (e.PropertyName == "StemDirection")
                SetLedgers();
            else if (e.PropertyName == "Articulation")
                LayoutArticulation();
        }

        /// <summary>
        /// Occurs when the <see cref="Model.Elements.CWNotation.Chord.Notes"/> collection has changed.
        /// </summary>
        /// <remarks>
        /// <b>Operation:</b><br/>
        /// The handler modifies the <see cref="Notes"/> collection of this element to reflect the adapted one,
        /// relayouts augmentation dots, restes ledger lines and raises the <see cref="ViewModelElement.PropertyChanged"/>
        /// event for the <see cref="StemStart"/>, <see cref="StemEnd"/> and <see cref="DurationalSymbol.CoreWidth"/> properties.
        /// </remarks>
        /// <param name="sender">Object that has sent the event</param>
        /// <param name="e">Event data</param>
        private void Notes_CollectionChanged(object sender, Model.Collections.NotifyCollectionChangedEventArgs e)
        {
            switch (e.Action)
            {
                case Model.Collections.NotifyCollectionChangedAction.Add:
                {
                    int index = e.NewStartingIndex;

                    foreach (Model.Elements.CWNotation.Note modelNote in e.NewItems)
                    {
                        var note = new Note(this, modelNote);
                        note.PropertyChanged += new PropertyChangedEventHandler(note_PropertyChanged);
                        notesCollection.Insert(index++, note);
                    }
                    break;
                }
                case Model.Collections.NotifyCollectionChangedAction.Move:
                    notesCollection.Move(e.OldStartingIndex, e.NewStartingIndex);
                    break;
                case Model.Collections.NotifyCollectionChangedAction.Remove:
                    notesCollection[e.OldStartingIndex].PropertyChanged -= note_PropertyChanged;
                    notesCollection.RemoveAt(e.OldStartingIndex);
                    break;
                case Model.Collections.NotifyCollectionChangedAction.Replace:
                {
                    notesCollection[e.OldStartingIndex].PropertyChanged -= note_PropertyChanged;
                    notesCollection.RemoveAt(e.OldStartingIndex);
                    var note = new Note(this, (Model.Elements.CWNotation.Note)e.NewItems[0]);
                    note.PropertyChanged += new PropertyChangedEventHandler(note_PropertyChanged);
                    notesCollection.Insert(e.NewStartingIndex, note);
                    break;
                }
                case Model.Collections.NotifyCollectionChangedAction.Reset:
                    foreach (var note in notesCollection)
                        note.PropertyChanged -= note_PropertyChanged;
                    notesCollection.Clear();
                    foreach (var note in ((Model.Elements.CWNotation.Chord)AdaptedElement).Notes)
                        notesCollection.Add(new Note(this, note));
                    break;
            }

            RaisePropertyChanged("CoreWidth");
            RaisePropertyChanged("StemStart");
            RaisePropertyChanged("StemEnd");
            RaisePropertyChanged("DotsOffset");

            SetLedgers();
        }

        /// <summary>
        /// Occurs when the value of some property of an owned note has changed.
        /// </summary>
        /// <param name="sender">Object that has sent the event</param>
        /// <param name="e">Event data</param>
        private void note_PropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            if (e.PropertyName == "StaffPosition")
            {
                SetLedgers();

                var note = sender as Note;
                if (note == notesCollection[0] && StemDirection == StemOrientation.Up)
                    RaisePropertyChanged("StemStart");
                else if (note == notesCollection[notesCollection.Count - 1] && StemDirection == StemOrientation.Down)
                    RaisePropertyChanged("StemStart");
            }
        }

        #endregion

        #region Private Methods

        /// <summary>
        /// Layouts the articulation symbol.
        /// </summary>
        private void LayoutArticulation()
        {
            if (notesCollection.Count == 0 || Articulation == ArticulationType.Legato)
                return;

            var adaptedChord = (Model.Elements.CWNotation.Chord)AdaptedElement;
            var voices = (from beat in adaptedChord.PresentedElement.FindOwner<Model.Elements.MusicalContentElement>().Beats
                          from symbol in beat.Symbols
                          select symbol.Voice).Distinct();

            var spaceSize = FindOwner<Staff>().SpaceSize;

            if (voices.Count() > 1)
            {
                if (StemDirection == StemOrientation.Up)
                {
                    if (StemEnd > FindOwner<Staff>().Top + spaceSize)
                        articulationPosition = notesCollection[0].VerticalPosition + spaceSize;
                    else
                        articulationPosition = StemEnd - spaceSize;

                    if (Articulation == ArticulationType.Staccatissimo || Articulation == ArticulationType.Portamento)
                        articulationPosition = Math.Min(FindOwner<Staff>().Top - spaceSize, articulationPosition);
                }
                else
                {
                    if (StemEnd > FindOwner<Staff>().Top + spaceSize)
                        articulationPosition = notesCollection[notesCollection.Count - 1].VerticalPosition - spaceSize;
                    else
                        articulationPosition = StemEnd + spaceSize;

                    if (Articulation == ArticulationType.Staccatissimo || Articulation == ArticulationType.Portamento)
                        articulationPosition = Math.Max(FindOwner<Staff>().Bottom + spaceSize, articulationPosition);
                }
            }
            else
            {
                if (StemDirection == StemOrientation.Up)
                    articulationPosition = notesCollection[0].VerticalPosition + spaceSize;
                else
                    articulationPosition = notesCollection[notesCollection.Count - 1].VerticalPosition - spaceSize;
            }

            RaisePropertyChanged("ArticulationPosition");
        }

        /// <summary>
        /// Modifies the <see cref="Flags"/> collection so that it contains the number of flags
        /// corresponding to the <see cref="DurationalSymbol.BasicDuration"/> of the chord.
        /// </summary>
        private void SetFlags()
        {
            var count = Math.Max(0, 5 - Model.Utils.Duration.HighestBit(BasicDuration));
            if (((Model.Elements.CWNotation.Chord)AdaptedElement).GroupBeam != null)
                count = 0;

            if (count > flagsCollection.Count)
            {
                if (flagsCollection.Count > 0)
                    flagsCollection[flagsCollection.Count - 1].IsLast = false;

                for (int i = flagsCollection.Count; i < count; ++i)
                    flagsCollection.Add(new Flag(this, i, false));

                flagsCollection[flagsCollection.Count - 1].IsLast = true;
            }
            else
            {
                for (int i = flagsCollection.Count - 1; i >= count; --i)
                    flagsCollection.RemoveAt(i);

                if (count > 0)
                    flagsCollection[flagsCollection.Count - 1].IsLast = true;
            }
        }

        /// <summary>
        /// Modifies the <see cref="LedgerLines"/> collection so that it contains
        /// the correct number of ledger lines that are well positioned.
        /// </summary>
        private void SetLedgers()
        {
            if (notesCollection.Count == 0)
            {
                ledgersCollection.Clear();
                ledgersBellowIndex = 0;
                return;
            }

            // PHASE I - Just ensure the correct number of ledger lines in the ledgersCollection

            var lastStaffPos = FindOwner<Staff>().Lines.Count * 2 - 2;
            var countAbove = Math.Max(0, (notesCollection[notesCollection.Count - 1].StaffPosition - lastStaffPos) / 2);
            var countBellow = Math.Max(0, (-notesCollection[0].StaffPosition) / 2);

            if (countAbove > ledgersBellowIndex)
            {
                for (int i = ledgersBellowIndex; i < countAbove; ++i)
                    ledgersCollection.Insert(i, new LedgerLine(this));
                ledgersBellowIndex = countAbove;
            }
            else if (countAbove < ledgersBellowIndex)
            {
                for (int i = ledgersBellowIndex - 1; i >= countAbove; --i)
                    ledgersCollection.RemoveAt(i);
                ledgersBellowIndex = countAbove;
            }

            if (countBellow > ledgersCollection.Count - ledgersBellowIndex)
            {
                for (int i = ledgersCollection.Count - countAbove; i < countBellow; ++i)
                    ledgersCollection.Add(new LedgerLine(this));
            }
            else if (countBellow < ledgersCollection.Count - ledgersBellowIndex)
            {
                for (int i = ledgersCollection.Count - 1; i >= ledgersBellowIndex + countBellow; --i)
                    ledgersCollection.RemoveAt(i);
            }

            if (ledgersCollection.Count == 0)
                ledgersBellowIndex = 0;

            // PHASE II - Set the parameters of the ledger lines, first those above the staff and then bellow.

            var spaceSize = FindOwner<Staff>().SpaceSize;
            int staffSpaces = FindOwner<Staff>().Lines.Count - 1;
            double typeHalfSpace;
            double x1 = double.PositiveInfinity, x2 = 0;
            int staffPosition = lastStaffPos + countAbove * 2;
            int nextNoteIndex = notesCollection.Count - 1;
            var myPosition = Position;

            for (int i = ledgersBellowIndex - 1; i >= 0; --i)
            {
                if (nextNoteIndex >= 0 && staffPosition <= notesCollection[nextNoteIndex].StaffPosition)
                {
                    typeHalfSpace = (staffSpaces > 0 ? notesCollection[nextNoteIndex].EffectiveTypeSize / staffSpaces : -spaceSize) / 2;
                    double tmpX1 = notesCollection[nextNoteIndex].HeadPosition - typeHalfSpace - myPosition;
                    double tmpX2 = notesCollection[nextNoteIndex].HeadPosition + notesCollection[nextNoteIndex].Width + typeHalfSpace - myPosition;
                    x1 = Math.Min(x1, tmpX1);
                    x2 = Math.Max(x2, tmpX2);
                    --nextNoteIndex;
                }

                ledgersCollection[i].X1 = x1;
                ledgersCollection[i].X2 = x2;
                ledgersCollection[i].Y = i + 1;

                staffPosition -= 2;
            }

            staffPosition = -countBellow * 2;
            nextNoteIndex = 0;
            
            for (int i = ledgersCollection.Count - 1; i >= ledgersBellowIndex; --i)
            {
                if (nextNoteIndex < notesCollection.Count && staffPosition >= notesCollection[nextNoteIndex].StaffPosition)
                {
                    typeHalfSpace = (staffSpaces > 0 ? notesCollection[nextNoteIndex].EffectiveTypeSize / staffSpaces : -spaceSize) / 2;
                    double tmpX1 = notesCollection[nextNoteIndex].HeadPosition - typeHalfSpace - myPosition;
                    double tmpX2 = notesCollection[nextNoteIndex].HeadPosition + notesCollection[nextNoteIndex].Width + typeHalfSpace - myPosition;
                    x1 = Math.Min(x1, tmpX1);
                    x2 = Math.Max(x2, tmpX2);
                    ++nextNoteIndex;
                }

                ledgersCollection[i].X1 = x1;
                ledgersCollection[i].X2 = x2;
                ledgersCollection[i].Y = -i - 1 + ledgersBellowIndex;

                staffPosition += 2;
            }
        }

        #endregion

        #region Public Methods

        /// <summary>
        /// Creates an undoable command for setting the <see cref="Articulation"/> of this chord.
        /// </summary>
        /// <param name="articulation">
        /// <para>
        /// Type: <see cref="ArticulationType"/>
        /// </para>
        /// <para>
        /// The articulation.
        /// </para>
        /// </param>
        /// <returns>
        /// <para>
        /// Type: <see cref="UndoableCommandBase"/>
        /// </para>
        /// <para>
        /// The created command ready for execution.
        /// </para>
        /// </returns>
        public UndoableCommandBase CreateSetArticulationCommand(ArticulationType articulation)
        {
            var modelArticulation = (Model.Elements.ArticulationType)Enum.Parse(typeof(Model.Elements.ArticulationType), articulation.ToString());

            return new SetterCommand<Model.Elements.ArticulationType>(this, "Articulation", modelArticulation, UndoStack, RedoStack)
            {
                Name = "Change Articulation"
            };
        }

        /// <summary>
        /// Creates an undoable command for setting the <see cref="StemDirection"/> property.
        /// </summary>
        /// <param name="direction">
        /// <para>
        /// Type: <see cref="StemOrientation"/>
        /// </para>
        /// <para>
        /// The new stem direction.
        /// </para>
        /// </param>
        /// <returns>
        /// <para>
        /// Type: <see cref="UndoableCommandBase"/>
        /// </para>
        /// <para>
        /// The created command ready for execution.
        /// </para>
        /// </returns>
        public UndoableCommandBase CreateSetStemDirectionCommand(StemOrientation direction)
        {
            var modelDirection = (Model.Elements.CWNotation.StemOrientation)Enum.Parse(typeof(Model.Elements.CWNotation.StemOrientation), direction.ToString());

            return new SetterCommand<Model.Elements.CWNotation.StemOrientation>(this, "StemDirection", modelDirection, UndoStack, RedoStack);
        }

        /// <summary>
        /// Restores a given note.
        /// </summary>
        /// <remarks>
        /// This method is intended as a temporary solution to the scenario when the user redoes
        /// a previously undone note insertion operation.
        /// </remarks>
        /// <param name="note">
        /// <para>
        /// Type: <see cref="Note"/>
        /// </para>
        /// <para>
        /// The restored note.
        /// </para>
        /// </param>
        /// <exception cref="ArgumentNullException">
        /// The <paramref name="note"/> is <see langword="null"/>.
        /// </exception>
        /// <exception cref="ArgumentException">
        /// The <paramref name="note"/> does not belong to this chord.<br/>
        /// -or-<br/>
        /// The chord does not contain any note that could be replaced by the restored <paramref name="note"/>.
        /// </exception>
        public void RestoreNote(Note note)
        {
            if (note == null)
                throw new ArgumentNullException("note");
            if (note.FindOwner<Chord>() != this)
                throw new ArgumentException("The restored note does not belong to this chord.", "note");

            var replaced = notesCollection.FirstOrDefault(n => n.AdaptedElement == note.AdaptedElement);
            if (replaced == null)
                throw new ArgumentException("The chord does not contain any note that could be replaced by the restored note.", "note");
            replaced.PropertyChanged -= note_PropertyChanged;
            notesCollection[notesCollection.IndexOf(replaced)] = note;
            note.PropertyChanged += new PropertyChangedEventHandler(note_PropertyChanged);
        }

        #endregion

        #region Public Properties

        /// <summary>
        /// Gets or sets the articulation that this chord should be performed with.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="ArticulationType"/>
        /// </para>
        /// <para>
        /// The articulation that this chord should be performed with.
        /// </para>
        /// </value>
        public ArticulationType Articulation
        {
            get { return (ArticulationType)Enum.Parse(typeof(ArticulationType), GetPropertyValue<Model.Elements.ArticulationType>("Articulation").ToString()); }
            set
            {
                if (UnsavedChanges.ContainsKey("Articulation"))
                {
                    var cmd = (SetterCommand<Model.Elements.ArticulationType>)UnsavedChanges["Articulation"];
                    cmd.Value = (Model.Elements.ArticulationType)Enum.Parse(typeof(Model.Elements.ArticulationType), value.ToString());
                }
                else
                    UnsavedChanges["Articulation"] = CreateSetArticulationCommand(value);

                RaisePropertyChanged("Articulation");
            }
        }

        /// <summary>
        /// Gets the vertical position of the articulation symbol.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="Double"/>
        /// </para>
        /// <para>
        /// The offset (in device independent pixels) of the articulation symbol
        /// from the top edge of the owner section.
        /// </para>
        /// </value>
        public double ArticulationPosition
        {
            get { return articulationPosition; }
        }

        /// <summary>
        /// Gets the collection of flags on this chord's stem.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="ReadOnlyObservableCollection{T}"/> of <see cref="Flag"/>
        /// </para>
        /// <para>
        /// The collection of flags on this chord's stem.
        /// </para>
        /// </value>
        public ReadOnlyObservableCollection<Flag> Flags
        {
            get { return flags; }
        }

        /// <summary>
        /// Gets the collection of ledger lines belonging to this chord.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="ReadOnlyObservableCollection{T}"/> of <see cref="LedgerLine"/>
        /// </para>
        /// <para>
        /// The collection of ledger lines belonging to this chord.
        /// </para>
        /// </value>
        public ReadOnlyObservableCollection<LedgerLine> LedgerLines
        {
            get { return ledgerLines; }
        }

        /// <summary>
        /// Gets the collection of notes in this chord.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="ReadOnlyObservableCollection{T}"/> of <see cref="Note"/>
        /// </para>
        /// <para>
        /// The notes in this chord.
        /// </para>
        /// </value>
        public ReadOnlyObservableCollection<Note> Notes
        {
            get { return notes; }
        }

        /// <summary>
        /// Gets or sets the direction of the stem.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="StemOrientation"/>
        /// </para>
        /// <para>
        /// The direction of the stem.
        /// </para>
        /// </value>
        public StemOrientation StemDirection
        {
            get
            {
                var modelOrientation = GetPropertyValue<Model.Elements.CWNotation.StemOrientation>("StemDirection");

                return (StemOrientation)Enum.Parse(typeof(StemOrientation), modelOrientation.ToString());
            }
            set
            {
                if (UnsavedChanges.ContainsKey("StemDirection"))
                {
                    var cmd = (SetterCommand<Model.Elements.CWNotation.StemOrientation>)UnsavedChanges["StemDirection"];
                    cmd.Value = (Model.Elements.CWNotation.StemOrientation)Enum.Parse(typeof(Model.Elements.CWNotation.StemOrientation), value.ToString());
                }
                else
                    UnsavedChanges["StemDirection"] = CreateSetStemDirectionCommand(value);

                RaisePropertyChanged("StemDirection");
            }
        }

        /// <summary>
        /// Gets the Y coordinate of the stem ending point.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="Double"/>
        /// </para>
        /// <para>
        /// The distance (in device independent pixels) between the stem ending point
        /// and the top section's edge.
        /// </para>
        /// </value>
        public double StemEnd
        {
            get
            {
                if (StemDirection == StemOrientation.Down)
                    return StemStart + StemLength;
                else
                    return StemStart - StemLength;
            }
        }

        /// <summary>
        /// Gets the length of the stem.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="Double"/>
        /// </para>
        /// <para>
        /// The distance (in device independent pixels) between the stem starting and ending points.
        /// </para>
        /// </value>
        public double StemLength
        {
            get
            {
                var modelLength = GetPropertyValue<double>("StemLength");

                return (double)LengthConverter.Convert(modelLength, typeof(double), null, CultureInfo.CurrentCulture);
            }
        }

        /// <summary>
        /// Gets the Y coordinate of the stem starting point.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="Double"/>
        /// </para>
        /// <para>
        /// The distance (in device independent pixels) between the stem starting point
        /// and the top section's edge.
        /// </para>
        /// </value>
        public double StemStart
        {
            get 
            {
                if (notesCollection.Count == 0)
                    return 0.0;

                if (StemDirection == StemOrientation.Down)
                {
                    var start = notesCollection[notesCollection.Count - 1].VerticalPosition;

                    return start;
                }
                else
                {
                    var start = notesCollection[0].VerticalPosition;
                    
                    return start;
                }
            }
        }

        #endregion

        #region Private Fields

        /// <summary>
        /// Holds the value of the <see cref="ArticulationPosition"/> property.
        /// </summary>
        private double articulationPosition;
        /// <summary>
        /// Holds a user-friendly name of this element.
        /// </summary>
        private const string elementNameKey = "ViewModel.ElementNames.Chord";
        /// <summary>
        /// Holds the value of the <see cref="Flags"/> property.
        /// </summary>
        private ReadOnlyObservableCollection<Flag> flags;
        /// <summary>
        /// Holds the collection of flags on this chord's stem.
        /// </summary>
        private ObservableCollection<Flag> flagsCollection;
        /// <summary>
        /// Holds the value of the <see cref="LedgerLines"/> property.
        /// </summary>
        private ReadOnlyObservableCollection<LedgerLine> ledgerLines;
        /// <summary>
        /// Holds the index of the first ledger line bellow the staff.
        /// </summary>
        /// <remarks>
        /// The <see cref="ledgersCollection"/> is split into two parts:
        /// Ledger lines above and bellow the staff. The division is given
        /// by this index.
        /// </remarks>
        private int ledgersBellowIndex;
        /// <summary>
        /// Holds the collection of ledger lines belonging to this chord.
        /// </summary>
        private ObservableCollection<LedgerLine> ledgersCollection;
        /// <summary>
        /// Holds the value of the <see cref="Notes"/> property.
        /// </summary>
        private ReadOnlyObservableCollection<Note> notes;
        /// <summary>
        /// Holds the collection of the notes in this chord.
        /// </summary>
        private ObservableCollection<Note> notesCollection;

        #endregion
    }

    /// <summary>
    /// Determines the orientation of a <see cref="Chord"/>'s (note's) stem.
    /// </summary>
    public enum StemOrientation
    {
        /// <summary>
        /// The stem points up.
        /// </summary>
        Up,
        /// <summary>
        /// The stem points down.
        /// </summary>
        Down
    }

    /// <summary>
    /// Describes the type of the articulation that a <see cref="Chord"/>
    /// should be performed with.
    /// </summary>
    public enum ArticulationType
    {
        /// <summary>
        /// Legato.
        /// </summary>
        Legato,
        /// <summary>
        /// Staccato.
        /// </summary>
        Staccato,
        /// <summary>
        /// Staccatissimo.
        /// </summary>
        Staccatissimo,
        /// <summary>
        /// Portamento.
        /// </summary>
        Portamento,
        /// <summary>
        /// Tenuto.
        /// </summary>
        Tenuto
    }
}
